package com.keqiang.chart.impl.axis.impl.pseudo3d.bar

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.TextPaint
import com.keqiang.chart.animation.Animator
import com.keqiang.chart.impl.axis.Axis
import com.keqiang.chart.impl.axis.impl.bar.BarGroup
import com.keqiang.chart.impl.axis.impl.line.AddedLine
import com.keqiang.chart.impl.axis.render.BaseDataRender
import com.keqiang.chart.utils.ObjectPool
import com.keqiang.chart.utils.draw3DRectangle
import com.keqiang.chart.utils.toRadian
import com.keqiang.utils.PaintUtils.getChartHeight
import kotlin.math.cos
import kotlin.math.sin

/**
 * 绘制柱状图柱形图
 *
 * @author Created by wanggaowan on 2022/7/26 11:33
 */
open class BarChart3DataRender(chart: BarChart3D) : BaseDataRender<BarChart3D>(chart) {

    override fun onDraw(canvas: Canvas, start: Float, top: Float, end: Float, bottom: Float,
                        gridWidth: Float, gridHeight: Float, animator: Animator?) {
        val barData = chart.data ?: return

        canvas.save()
        canvas.clipRect(start - chart.xAxis.startPadding,
            top - chart.yAxis.endPadding,
            end + chart.xAxis.endPadding + cos(chart.faceDegrees.toRadian) * chart.xAxis.lineWidth,
            bottom + chart.yAxis.startPadding)

        barData.groups?.let {
            drawBar(canvas, it, start, gridWidth, top, bottom, animator)
            drawValueText(canvas, it, top)
        }

        if ((animator?.getProgress() ?: 1f) == 1f) {
            barData.addedLines?.let {
                val faceRadian = chart.faceDegrees.toRadian
                val xOffset = cos(faceRadian) * chart.xAxis.lineWidth
                val yOffset = sin(faceRadian) * chart.xAxis.lineWidth
                drawAddedLine(canvas, it, start + xOffset, end + xOffset, top, bottom - yOffset)
            }
        }

        canvas.restore()

        if ((animator?.getProgress() ?: 1f) == 1f) {
            chart.touchHandler?.touchData?.let {
                chart.highLightRender?.onDraw(canvas, start, top, end, bottom, it)
            }
        }
    }

    /**
     * 绘制柱状图
     */
    protected open fun drawBar(canvas: Canvas, dataList: List<BarGroup>, start: Float, gridWidth: Float, top: Float, bottom: Float, animator: Animator?) {
        val yAxis = chart.yAxis
        val xAxis = chart.xAxis
        var tempStart = start
        val maxValue = yAxis.max - yAxis.min
        val faceRadian = chart.faceDegrees.toRadian
        val faceCos = cos(faceRadian) * xAxis.lineWidth
        val faceSin = sin(faceRadian) * xAxis.lineWidth
        val range = bottom - top - faceSin

        val paint = ObjectPool.obtainPaint<Paint>()
        paint.style = Paint.Style.FILL

        for (data in dataList) {
            if (data.values.isEmpty()) {
                continue
            }

            var totalPercent = 0f
            data.values.forEach { entity ->
                totalPercent += entity.widthPercent
            }

            val width = gridWidth - data.barOffset * (data.values.size - 1)
            var drawStart = if (totalPercent >= 1 || data.gravity == BarGroup.GRAVITY_START) {
                tempStart
            } else if (data.gravity == BarGroup.GRAVITY_END) {
                tempStart + width * (1 - totalPercent)
            } else {
                tempStart + width * (1 - totalPercent) / 2f
            }

            data.values.forEach { entity ->
                val barWidth = entity.widthPercent * width
                val alpha = Color.alpha(entity.bgColor)
                if (alpha > 0) {
                    paint.shader = null
                    canvas.draw3DRectangle(paint, drawStart, bottom, barWidth, xAxis.lineWidth, range, entity.bgColor, chart.faceDegrees)
                }

                val barHeight =
                    (entity.value - yAxis.min) / maxValue * range * (animator?.getProgress() ?: 1f)
                entity.drawRectF.set(drawStart, bottom - barHeight - faceSin, drawStart + barWidth + faceCos, bottom)
                paint.shader =
                    entity.shaderFactory?.invoke(entity.drawRectF.left, top, entity.drawRectF.right, bottom)
                canvas.draw3DRectangle(paint, drawStart, bottom, barWidth, xAxis.lineWidth, barHeight, entity.color, chart.faceDegrees)
                drawStart += barWidth + data.barOffset
            }

            tempStart += gridWidth
        }

        ObjectPool.recyclePaint(paint)
    }

    /**
     * 绘制柱状图文本
     */
    protected open fun drawValueText(canvas: Canvas, dataList: List<BarGroup>, top: Float) {
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)
        textPaint.textAlign = Paint.Align.CENTER

        for (data in dataList) {
            if (data.values.isEmpty()) {
                continue
            }

            data.values.forEach { entity ->
                if (entity.drawLabel) {
                    val text = entity.label
                    if (!text.isNullOrEmpty()) {
                        textPaint.color = entity.labelColor
                        textPaint.textSize = entity.labelTextSize


                        val y = if (entity.labelFixTop) {
                            top - entity.labelOffset
                        } else {
                            var y2 = entity.drawRectF.top - entity.labelOffset
                            val textHeight = textPaint.getChartHeight(includePadding = false)
                            if (y2 - textHeight < top - chart.yAxis.endPadding) {
                                y2 = entity.drawRectF.top + textHeight + entity.labelOffset
                            }
                            y2
                        }

                        canvas.drawText(text, entity.drawRectF.left + entity.drawRectF.width() / 2, y, textPaint)
                    }
                }
            }
        }

        ObjectPool.recyclePaint(textPaint)
    }

    /**
     * [end]： 折线图X轴绘制区域结束点，不包括[Axis.endPadding]
     *
     * [top]： 折线图Y轴绘制区域起始点，不包括[Axis.endPadding]
     *
     * [animProgress]:以动画形式绘制，指定动画进度
     */
    protected open fun drawAddedLine(
        canvas: Canvas, addedLines: List<AddedLine>,
        start: Float, end: Float, top: Float,
        bottom: Float, animProgress: Float = 1f
    ) {

        val tempRectF = ObjectPool.obtainRectF()
        val tempPath = ObjectPool.obtainPath()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        canvas.save()
        tempRectF.set(start, top - chart.yAxis.endPadding, end, bottom)
        canvas.clipRect(tempRectF)

        val yAxis = chart.yAxis
        val xAxis = chart.xAxis
        addedLines.forEach {
            val y = bottom - ((it.value - yAxis.min) / (yAxis.max - yAxis.min) * (bottom - top))
            val stopX = if (it.useEndPadding) end else end + xAxis.endPadding
            val startX = if (it.useStartPadding) start else start - xAxis.startPadding
            textPaint.color = it.lineColor
            textPaint.strokeWidth = it.lineWidth
            textPaint.style = Paint.Style.STROKE
            textPaint.pathEffect = it.pathEffect
            tempPath.reset()
            tempPath.moveTo(startX, y)
            tempPath.lineTo(stopX * animProgress, y)
            canvas.drawPath(tempPath, textPaint)

            if (animProgress < 1f || it.label.isNullOrEmpty() || it.labelTextSize <= 0) {
                return@forEach
            }

            val isHorizontalCenter =
                it.labelGravity and android.view.Gravity.LEFT != android.view.Gravity.LEFT
                    && it.labelGravity and android.view.Gravity.RIGHT != android.view.Gravity.RIGHT
                    && it.labelGravity and android.view.Gravity.CENTER_HORIZONTAL == android.view.Gravity.CENTER_HORIZONTAL
            val isEnd =
                !isHorizontalCenter && (it.labelGravity and android.view.Gravity.RIGHT) == android.view.Gravity.RIGHT
            val isBottom =
                (it.labelGravity and android.view.Gravity.BOTTOM) == android.view.Gravity.BOTTOM

            textPaint.style = Paint.Style.FILL
            textPaint.pathEffect = null
            textPaint.color = it.labelTextColor
            textPaint.textSize = it.labelTextSize
            var textStartX: Float = it.labelXOffset
            textPaint.textAlign = when {
                isHorizontalCenter -> {
                    textStartX += startX + (stopX - startX) / 2f
                    Paint.Align.CENTER
                }

                isEnd -> {
                    textStartX += stopX
                    Paint.Align.RIGHT
                }

                else -> {
                    textStartX += startX
                    Paint.Align.LEFT
                }
            }

            val fm = textPaint.fontMetrics
            val drawTop: Float
            val drawBottom: Float
            if (isBottom) {
                drawTop = y + it.lineWidth / 2f
                drawBottom = drawTop + fm.descent - fm.ascent
            } else {
                drawBottom = y - it.lineWidth / 2f
                drawTop = drawBottom - (fm.descent - fm.ascent)
            }

            it.drawRectF.set(0f, drawTop, 0f, drawBottom)
            val textStartY = if (isBottom) {
                it.drawRectF.top - fm.ascent + it.labelYOffset
            } else {
                it.drawRectF.bottom - fm.descent - it.labelYOffset
            }

            textPaint.textSize = it.labelTextSize
            canvas.drawText(it.label!!, textStartX, textStartY, textPaint)
        }

        canvas.restore()

        ObjectPool.recycleRectF(tempRectF)
        ObjectPool.recyclePath(tempPath)
        ObjectPool.recyclePaint(textPaint)
    }
}